package com.yu8home.opcua;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.yu8home.opcua.tree.KepItem;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.model.nodes.objects.ServerTypeNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.Stack;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;

import java.util.List;
import java.util.Locale;
import java.util.Objects;

@Slf4j
public class OpcUAUtil {
    public static OpcUaClient opcUaClient = null;
    public static String endPointUrl = "opc.tcp://192.168.1.177:4840";
    public static int namespaceIndex = 2;


    /**
     * 连接
     */
    public static void getConnect() {
        long a = System.currentTimeMillis();
        try {
            opcUaClient = createClient();
            opcUaClient.connect().get();
            log.info("opcUA连接成功：{}", endPointUrl);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("opcUA连接失败：{}", endPointUrl);
        }
        log.info("connect耗时：{}", System.currentTimeMillis() - a);
    }


    /**
     * 创建 opc-ua 客户端
     *
     * @return
     * @throws Exception
     */
    public static OpcUaClient createClient() throws Exception {
        return OpcUaClient.create(endPointUrl,

                endpoints -> endpoints.stream()
                        .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
                        .findFirst(),

                configBuilder -> configBuilder.setApplicationName(new LocalizedText(Locale.CHINESE.getLanguage(), "aaaaaaa"))
                        .setApplicationUri("urn:eclipse:milo:example:opcUaClient")
                        .setIdentityProvider(new AnonymousProvider())
                        .setRequestTimeout(UInteger.valueOf(50 * 1000))
                        .build()
        );
    }


    public static void close() {
        try {
            opcUaClient.disconnect().get();
            Stack.releaseSharedResources();
            log.info("opcUA连接关闭-成功");
        } catch (Exception e) {
            log.info("opcUA连接关闭-失败");
        }
    }


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 遍历树形节点
     */
    public static void browseNodeTree(UaNode uaNode, List<KepItem> kepItemList) {
        try {
            List<? extends UaNode> nodes;
            if (uaNode == null) {
                nodes = opcUaClient.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
            } else {
                nodes = opcUaClient.getAddressSpace().browseNodes(uaNode);
            }

            for (UaNode t : nodes) {
                NodeId nodeId = t.getNodeId();

                String browseName = t.getBrowseName().getName();
                String identifier = nodeId.getIdentifier().toString();

                // 排除系统性节点，这些系统性节点名称一般都是以"_"开头
                if (t instanceof ServerTypeNode || Objects.requireNonNull(browseName).startsWith("_")) {
                    continue;
                }

                // 节点长度为3：通道.设备.标记
                String[] sArr = identifier.split("\\.");
                if (sArr.length == 3) {
                    Object _Description = readNode(identifier + "._Description");
                    Object _Address = readNode(identifier + "._Address");
                    String _RawDataType = readNode(identifier + "._RawDataType").toString();


                    KepItem kepItem = new KepItem();
                    kepItem.setShortName(browseName);
                    kepItem.setIdentifier(identifier);
                    kepItem.setRawDataType(_RawDataType);
                    kepItem.setAddress(StrUtil.toStringOrEmpty(_Address));
                    kepItem.setDescription(StrUtil.toStringOrEmpty(_Description));
                    kepItemList.add(kepItem);
                }
                browseNodeTree(t, kepItemList);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static Object readNode(String identifier) throws Exception {
        long x = System.currentTimeMillis();
        NodeId nodeId = new NodeId(namespaceIndex, identifier);
        DataValue dataValue = opcUaClient.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
        Object v = dataValue.getValue().getValue();

        log.info("{} = {}，耗时：{}", identifier, v, System.currentTimeMillis() - x);
        return v;
    }


    public static void writeNodeValue(String identifier, String value, String valueType) throws Exception {
        long x = System.currentTimeMillis();
        NodeId nodeId = new NodeId(namespaceIndex, identifier);
        DataValue nowValue = getDataValue(valueType, value);
        StatusCode statusCode = opcUaClient.writeValue(nodeId, nowValue).get();
        log.info("写数据，identifier：{}，isGood：{}，str：{}，耗时：{}", identifier, statusCode.isGood(), statusCode, System.currentTimeMillis() - x);
    }


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    /**
     * 根据“valueType、value”获取 写数据“DataValue”
     */
    public static DataValue getDataValue(String valueType, String value) {
        Object v;
        switch (valueType) {
            case DataType.BOOLEAN:
                v = Boolean.valueOf(value) || "1".equals(value);
                break;
            case "Char":
                v = Byte.valueOf(value);
                break;
            case DataType.BYTE:
                v = Unsigned.ubyte(value);
                break;
            case DataType.SHORT:
                v = Short.valueOf(value);
                break;
            case DataType.WORD:
                v = Unsigned.ushort(value);
                break;
            case DataType.LONG:
                v = Long.valueOf(value);
                break;
            case "DWord":
                v = Unsigned.ulong(value);
                break;
            case DataType.FLOAT:
                v = Float.valueOf(value);
                break;
            case "Double":
                v = Double.valueOf(value);
                break;
            case "DateTime":
                v = DateUtil.parse(value);
                break;
            default:
                v = String.valueOf(value);
                break;
        }
        return new DataValue(new Variant(v), null, null);
    }

}
